﻿// Copyright (c) Microsoft Corporation.  All rights reserved.
// This source code is made available under the terms of the Microsoft Public License (MS-PL)

using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using SQLite.WinRT.Linq.Common.Translation;

namespace SQLite.WinRT.Linq.Common.Expressions
{
    public static class DbExpressionExtensions
    {
        public static bool IsDbExpression(ExpressionType nodeType)
        {
            return (int) nodeType >= (int) DbExpressionType.Table;
        }

        public static SelectExpression SetColumns(this SelectExpression select, IEnumerable<ColumnDeclaration> columns)
        {
            return new SelectExpression(
                select.Alias,
                columns.OrderBy(c => c.Name),
                select.From,
                select.Where,
                select.OrderBy,
                select.GroupBy,
                select.IsDistinct,
                select.Skip,
                select.Take,
                select.IsReverse);
        }

        public static SelectExpression AddColumn(this SelectExpression select, ColumnDeclaration column)
        {
            var columns = new List<ColumnDeclaration>(select.Columns);
            columns.Add(column);
            return select.SetColumns(columns);
        }

        public static SelectExpression RemoveColumn(this SelectExpression select, ColumnDeclaration column)
        {
            var columns = new List<ColumnDeclaration>(select.Columns);
            columns.Remove(column);
            return select.SetColumns(columns);
        }

        public static string GetAvailableColumnName(this IList<ColumnDeclaration> columns, string baseName)
        {
            string name = baseName;
            int n = 0;
            while (!IsUniqueName(columns, name))
            {
                name = baseName + (n++);
            }
            return name;
        }

        private static bool IsUniqueName(IList<ColumnDeclaration> columns, string name)
        {
            foreach (ColumnDeclaration col in columns)
            {
                if (col.Name == name)
                {
                    return false;
                }
            }
            return true;
        }

        public static ProjectionExpression AddOuterJoinTest(this ProjectionExpression proj, Expression expression)
        {
            string colName = proj.Select.Columns.GetAvailableColumnName("Test");
            DbQueryType colType = DbTypeSystem.GetColumnType(expression.Type);
            SelectExpression newSource = proj.Select.AddColumn(new ColumnDeclaration(colName, expression, colType));
            Expression newProjector =
                new OuterJoinedExpression(new ColumnExpression(expression.Type, colType, newSource.Alias, colName),
                    proj.Projector);
            return new ProjectionExpression(newSource, newProjector, proj.Aggregator);
        }

        public static SelectExpression SetDistinct(this SelectExpression select, bool isDistinct)
        {
            if (select.IsDistinct != isDistinct)
            {
                return new SelectExpression(
                    select.Alias,
                    select.Columns,
                    select.From,
                    select.Where,
                    select.OrderBy,
                    select.GroupBy,
                    isDistinct,
                    select.Skip,
                    select.Take,
                    select.IsReverse);
            }
            return select;
        }

        public static SelectExpression SetReverse(this SelectExpression select, bool isReverse)
        {
            if (select.IsReverse != isReverse)
            {
                return new SelectExpression(
                    select.Alias,
                    select.Columns,
                    select.From,
                    select.Where,
                    select.OrderBy,
                    select.GroupBy,
                    select.IsDistinct,
                    select.Skip,
                    select.Take,
                    isReverse);
            }
            return select;
        }

        public static SelectExpression SetWhere(this SelectExpression select, Expression where)
        {
            if (where != select.Where)
            {
                return new SelectExpression(
                    select.Alias,
                    select.Columns,
                    select.From,
                    where,
                    select.OrderBy,
                    select.GroupBy,
                    select.IsDistinct,
                    select.Skip,
                    select.Take,
                    select.IsReverse);
            }
            return select;
        }

        public static SelectExpression SetOrderBy(this SelectExpression select, IEnumerable<OrderExpression> orderBy)
        {
            return new SelectExpression(
                select.Alias,
                select.Columns,
                select.From,
                select.Where,
                orderBy,
                select.GroupBy,
                select.IsDistinct,
                select.Skip,
                select.Take,
                select.IsReverse);
        }

        public static SelectExpression AddOrderExpression(this SelectExpression select, OrderExpression ordering)
        {
            var orderby = new List<OrderExpression>();
            if (select.OrderBy != null)
            {
                orderby.AddRange(select.OrderBy);
            }
            orderby.Add(ordering);
            return select.SetOrderBy(orderby);
        }

        public static SelectExpression RemoveOrderExpression(this SelectExpression select, OrderExpression ordering)
        {
            if (select.OrderBy != null && select.OrderBy.Count > 0)
            {
                var orderby = new List<OrderExpression>(select.OrderBy);
                orderby.Remove(ordering);
                return select.SetOrderBy(orderby);
            }
            return select;
        }

        public static SelectExpression SetGroupBy(this SelectExpression select, IEnumerable<Expression> groupBy)
        {
            return new SelectExpression(
                select.Alias,
                select.Columns,
                select.From,
                select.Where,
                select.OrderBy,
                groupBy,
                select.IsDistinct,
                select.Skip,
                select.Take,
                select.IsReverse);
        }

        public static SelectExpression AddGroupExpression(this SelectExpression select, Expression expression)
        {
            var groupby = new List<Expression>();
            if (select.GroupBy != null)
            {
                groupby.AddRange(select.GroupBy);
            }
            groupby.Add(expression);
            return select.SetGroupBy(groupby);
        }

        public static SelectExpression RemoveGroupExpression(this SelectExpression select, Expression expression)
        {
            if (select.GroupBy != null && select.GroupBy.Count > 0)
            {
                var groupby = new List<Expression>(select.GroupBy);
                groupby.Remove(expression);
                return select.SetGroupBy(groupby);
            }
            return select;
        }

        public static SelectExpression SetSkip(this SelectExpression select, Expression skip)
        {
            if (skip != select.Skip)
            {
                return new SelectExpression(
                    select.Alias,
                    select.Columns,
                    select.From,
                    select.Where,
                    select.OrderBy,
                    select.GroupBy,
                    select.IsDistinct,
                    skip,
                    select.Take,
                    select.IsReverse);
            }
            return select;
        }

        public static SelectExpression SetTake(this SelectExpression select, Expression take)
        {
            if (take != select.Take)
            {
                return new SelectExpression(
                    select.Alias,
                    select.Columns,
                    select.From,
                    select.Where,
                    select.OrderBy,
                    select.GroupBy,
                    select.IsDistinct,
                    select.Skip,
                    take,
                    select.IsReverse);
            }
            return select;
        }

        public static SelectExpression AddRedundantSelect(this SelectExpression sel, TableAlias newAlias)
        {
            IEnumerable<ColumnDeclaration> newColumns = from d in sel.Columns
                let qt =
                    (d.Expression is ColumnExpression)
                        ? ((ColumnExpression) d.Expression).QueryType
                        : DbTypeSystem.GetColumnType(d.Expression.Type)
                select
                    new ColumnDeclaration(d.Name, new ColumnExpression(d.Expression.Type, qt, newAlias, d.Name), qt);

            var newFrom = new SelectExpression(
                newAlias,
                sel.Columns,
                sel.From,
                sel.Where,
                sel.OrderBy,
                sel.GroupBy,
                sel.IsDistinct,
                sel.Skip,
                sel.Take,
                sel.IsReverse);
            return new SelectExpression(sel.Alias, newColumns, newFrom, null, null, null, false, null, null, false);
        }

        public static SelectExpression RemoveRedundantFrom(this SelectExpression select)
        {
            var fromSelect = select.From as SelectExpression;
            if (fromSelect != null)
            {
                return SubqueryRemover.Remove(select, fromSelect);
            }
            return select;
        }

        public static SelectExpression SetFrom(this SelectExpression select, Expression from)
        {
            if (select.From != from)
            {
                return new SelectExpression(
                    select.Alias,
                    select.Columns,
                    from,
                    select.Where,
                    select.OrderBy,
                    select.GroupBy,
                    select.IsDistinct,
                    select.Skip,
                    select.Take,
                    select.IsReverse);
            }
            return select;
        }
    }
}